/* ==============================================================================
*
*   Copyright (c) 2010, Pierre Favier
*   All rights reserved.
*
*   NTasking is free software: you can redistribute it and/or modify it
*   under the terms of the GNU General Public License as published by the
*   Free Software Foundation.
*
*   Redistribution and use in source and binary forms, with or without
*   modification, are permitted provided that the following conditions
*   are met:
*
*   -   Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*
*   -   Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials
*       provided with the distribution.
*
*   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
*   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
*   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
*   BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
*   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
*   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
*   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
*   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
*   TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
*   THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
*   SUCH DAMAGE.
*
* ============================================================================== */

using System;
using NTasking.ValidationTests.ACATS_3_0.Support;
using NUnit.Framework;
using Rhino.Mocks;

namespace NTasking.ValidationTests.ACATS_3_0.C9
{

    [TestFixture(Description = "")]
    public class C940010 : ValidationTest
    {

        //      Use a small fragment of code from the simulation of a freeway meter
        //      used in c940007. Create three individual tasks which will be queued on
        //      the entry as the barrier is set.  Release them one at a time.  A
        //      procedure which is called within the entry has been modified for this
        //      test to raise a different exception for each pass through.  Check that
        //      all expected exceptions are raised and propagated.


        private const int tcExpectedPassageTotal = 669;


        private static bool tcFailed1 = false;
        private static int? tcInt = null;


        private static Ramp31 ramp31;


        private class Ramp31 : Protected
        {

            private bool releaseOneVehicle = false;
            private bool meterInUse = true;

            private int tcMultiplier = 1;
            private int tcPassageTotal = 0;
            // Use this to cycle through the required exceptions
            private int tcCurrentException = 0;

            [Entry]
            internal Action WaitAtMeter { get; private set; }


            [ReadWriteOperation]
            internal Action Pulse { get; private set; }

            [ReadWriteOperation]
            internal Action<int> TcPassage { get; private set; }

            [ReadWriteOperation]
            internal Action AddMeterQueue { get; private set; }

            [ReadWriteOperation]
            internal Action SubtractMeterQueue { get; private set; }


            [ReadOnlyOperation]
            internal Func<bool> MeterInUseState { get; private set; }

            [ReadOnlyOperation]
            internal Func<int> TcGetPassageTotal { get; private set; }

            [ReadOnlyOperation]
            internal Func<int> TcGetCurrentException { get; private set; }


            public Ramp31()
            {
                // Trace the paths through the various routines by totaling the
                // weighted call parameters
                ReadWriteOperation(TcPassage, Is: passPoint =>
                {
                    tcPassageTotal += passPoint * tcMultiplier;
                });


                // For the final check of the whole test
                ReadOnlyOperation(TcGetPassageTotal, Is: () =>
                {
                    return tcPassageTotal;
                });


                ReadOnlyOperation(TcGetCurrentException, Is: () =>
                {
                    return tcCurrentException;
                });


                // External call for Meter_in_Use
                ReadOnlyOperation(MeterInUseState, Is: () =>
                {
                    return meterInUse;
                });


                // Simulate the effects of the regular signal pulse 
                ReadWriteOperation(Pulse, Is: () =>
                {
                    releaseOneVehicle = true;
                });


                // Keep count of vehicles currently on meter queue - we can't use
                // the 'count because we need the outcall trigger
                ReadWriteOperation(AddMeterQueue, Is: () =>
                {
                    ; // :::: stub
                });


                // This routine has been modified to raise the required exceptions
                ReadWriteOperation(SubtractMeterQueue, Is: () =>
                {
                    const int tcPassPoint1 = 10;
                    const int tcPassPoint2 = 20;
                    const int tcPassPoint3 = 30;
                    const int tcPassPoint9 = 1000; // error

                    // Cycle through the required exceptions, one per call
                    tcCurrentException += 1;
                    if (tcCurrentException > 3)
                        throw new NullReferenceException();

                    switch (tcCurrentException)
                    {
                        case 1:
                            TcPassage(tcPassPoint1); // note passage through here
                            throw new AccessViolationException(); // Predefined exception
                        case 2:
                            TcPassage(tcPassPoint2); // note passage through here
                            throw new ProgramError(0, ""); // User defined exception
                        case 3:
                            TcPassage(tcPassPoint3); // note passage through here
                            tcCurrentException += tcInt.Value;
                            break;
                        default:
                            // Set flag for Report.Failed which cannot be called from
                            // within a Protected Object
                            tcFailed1 = true;
                            break;
                    }

                    TcPassage(tcPassPoint9); // note passage through here
                });


                // Here each Vehicle task queues itself awaiting release
                Entry(WaitAtMeter,
                    When: () => releaseOneVehicle,
                Is: () =>
                // EXAMPLE OF ENTRY WITH BARRIERS AND PERSISTENT SIGNAL
                {
                    const int tcPassPoint = 2;

                    TcPassage(tcPassPoint); // note passage through here
                    releaseOneVehicle = false; // Consume the signal
                    // Decrement number of vehicles on ramp 
                    SubtractMeterQueue(); // CALL PROCEDURE FROM WITHIN ENTRY BODY
                });
            }


            private void StartMeter()
            {
                meterInUse = true;
                ; // stub  :::: trigger the metering hardware
            }
        }


        // Carrier task. One is created for each vehicle arriving at the ramp
        private class Vehicle31 : ValidationTestTask
        {
            private const int tcPassPoint1 = 100;
            private const int tcPassPoint2 = 200;
            private const int tcPassPoint3 = 300;
            private Ramp31 _ramp31;


            internal Vehicle31(IValidationTestResult result, Ramp31 ramp31)
                : base(result)
            {
               _ramp31 = ramp31;
            }


            protected override void Body()
            {
                try
                {
                    if (_ramp31.MeterInUseState())
                    {
                        // Increment count of number of vehicles on ramp 
                        _ramp31.AddMeterQueue(); // CALL a protected PROCEDURE which is also called from within
                        // enter the meter queue
                        _ramp31.WaitAtMeter(); // CALL a protected ENTRY
                        Console.WriteLine("Exception not propagated back");
                        Result.TestFailed();
                    }

                    // :::: call to the first in the series of the Ramp_Sensors
                    //      this "passes" the vehicle from one sensor to the next
                }
                catch (AccessViolationException)
                {
                    _ramp31.TcPassage(tcPassPoint1); // note passage
                }
                catch (ProgramError)
                {
                    _ramp31.TcPassage(tcPassPoint2); // note passage
                }
                catch (InvalidOperationException)
                {
                    _ramp31.TcPassage(tcPassPoint3); // note passage
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Unexpected exception in Vehicle31 Task");
                    Console.WriteLine(ex);
                    Result.TestFailed();
                }
            }
        }


        // Simulate the arrival of a vehicle at the Ramp_Receiver and the
        // generation of an accompanying carrier task
        private static void NewArrival(IValidationTestResult result, Ramp31 ramp31)
        {
            const int tcPassPoint = 1;

            //new Vehicle31(result, ramp31).Start();
            new Vehicle31(result, ramp31).Activate();
            ramp31.TcPassage(tcPassPoint); // Note passage through here
        }


        [Test(Description =
                "Check that if an exception is raised during the execution of an " +
                "entry body it is propagated back to the caller")]
        public void Test()
        {
            IValidationTestResult result = Mocks.DynamicMock<IValidationTestResult>();
            
            tcFailed1 = false;

            Ada.Declare(() =>
            {
                ramp31 = new Ramp31();

                // Setup text expectations

                Expect.Call(result.TestFailed).Repeat.Never();
                Mocks.ReplayAll();
            },
            Begin: () =>
            {
                // Start test execution

                // Test driver.  This is ALL test control code

                // Create three independent tasks which will queue themselves on the
                // entry.  Each task will get a different exception
                NewArrival(result, ramp31);
                NewArrival(result, ramp31);
                NewArrival(result, ramp31);

                Ada.Delay(Impdef.ClearReadyQueue);

                // Set the barrier condition of the entry true, releasing one task
                ramp31.Pulse();
                Ada.Delay(Impdef.ClearReadyQueue);

                ramp31.Pulse();
                Ada.Delay(Impdef.ClearReadyQueue);

                ramp31.Pulse();
                Ada.Delay(Impdef.ClearReadyQueue);

                if (tcExpectedPassageTotal != ramp31.TcGetPassageTotal() ||
                    // Note: We are not really interested in this next check.  It is 
                    //       here to ensure the earlier statements which raised the 
                    //       Constraint_Error are not optimized out
                    ramp31.TcGetCurrentException() != 3)
                {
                    Console.WriteLine("Unexpected paths taken");
                    result.TestFailed();
                }

                if (tcFailed1)
                {
                    Console.WriteLine("Bad path through Subtract_Meter_Queue");
                    result.TestFailed();
                }
            });
        }

    }

}
